package com.nulldev.util.web.YouTubeExtractor.security;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.List;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.Arguments.ArgumentInit;
import com.nulldev.util.JVM.Arguments.ParsedArguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.JVM.arguments.ArgumentWithValue;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.MathUtil.TimeUtil;
import com.nulldev.util.VariableAPI.util.strings.splitters.Splitters;
import com.nulldev.util.codecs.uniapi.UniBase64;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.data.CachedItem;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.io.FastIO.FastIO;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.HttpClient.HttpClient;
import com.nulldev.util.web.HttpClient.HttpResponse;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractor;
import com.nulldev.util.web.YouTubeExtractor.extractors.IYouTubeExtractor;
import com.nulldev.util.web.YouTubeExtractor.extractors.IYouTubeExtractorConstants;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeVideoHeatmap;
import com.nulldev.util.web.YouTubeExtractor.video.ExtractedVideo;
import com.nulldev.util.web.YouTubeExtractor.video.YouTubeVideo;

public class SAPISIDHash {

	private static final Logger log = LoggerFactory.getLogger();
	private static final CachedItem<String> SAPI_HASHED = new CachedItem<String>("YTE_SAPI", TimeUtil.fromHours(1)) {

		@Override
		public String accquire() throws Exception {
			if (true)
				return null;
			final long pre_req_timestamp = System.currentTimeMillis();
			final HttpResponse<String> resp = HttpClient.getString().url("https://www.youtube.com/").headers(YouTubeExtractor.DEFAULT_COOKIES).create();
			final List<String> cookies = resp.cookiesAsList();
			IOUtils.closeQuietly(resp);
			getFromCookies(pre_req_timestamp, cookies);
			return null;
		}
	};

	public static class SAPIISIDInfo {
		public final String hash;
		public final String timestamp;
		public final String origin;

		public SAPIISIDInfo(final String hash, final String timestamp, final String origin) {
			this.hash = hash;
			this.timestamp = timestamp;
			this.origin = origin;
		}

		public String makeHash() {
			final String input = this.timestamp + " " + this.hash + " " + this.origin;
			return hashString(input);
		}

		public String makeHashedHeader() {
			final String input = this.timestamp + " " + this.hash + " " + this.origin;
			return "SAPISIDHASH " + this.timestamp + "_" + hashString(input);
		}

		public String getHash() {
			return this.hash;
		}

		public String getTimestamp() {
			return this.timestamp;
		}

		public String getOrigin() {
			return this.origin;
		}
	}

	private static String hashString(final String password) {
		try {
			final MessageDigest crypt = MessageDigest.getInstance("SHA-1");
			crypt.reset();
			crypt.update(password.getBytes("UTF-8"));
			final String output = byteToHex(crypt.digest());
			return output;
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}

	private static String byteToHex(final byte[] hash) {
		final Formatter formatter = new Formatter();
		for (final byte b : hash) {
			formatter.format("%02x", b);
		}
		final String result = formatter.toString();
		IOUtils.closeQuietly(formatter);
		return result;
	}

	public static SAPIISIDInfo getFromCookies(final long pre_req_timestamp, final List<String> cookies) {
		String wantedCookie = null;
		for (final String cookie : cookies) {
			if (!cookie.startsWith("__Secure-3PAPISID") && !cookie.startsWith("SAPISID"))
				continue;
			else {
				wantedCookie = cookie;
				break;
			}
		}
		if (wantedCookie == null) {
			log.warn("Failed to find correct cookie, not the end of the world.");
			return null;
		}
		if (wantedCookie.startsWith("SAPISID")) {
			// No work to do, skip.
		} else {
			// No work to do with __SECURE.
		}
		final String[] cookie = Splitters.splitString(wantedCookie, "=");
		final String value = cookie[1];
		return new SAPIISIDInfo(value, String.valueOf(pre_req_timestamp), "www.youtube.com");
	}

	private static final ArgumentWithValue<String> COOKIE_PAYLOAD_B64 = new ArgumentWithValue.StringArgument("-b64-cookie",
			"document.cookie value, encoded into Base64", "", false);
	private static final ArgumentWithValue<String> USER_ID = new ArgumentWithValue.StringArgument("-user-id", "YouTube User ID for X-Youtube-Identity-Token.",
			"", false);

	public static void main(String[] args) throws Exception {
		Arguments.argumentInit(new ArgumentInit() {

			@Override
			public void onInit(final Arguments args) {
				args.addArgument(COOKIE_PAYLOAD_B64);
				args.addArgument(USER_ID);
			}
		});
		final ParsedArguments pa = Arguments.setup(args);
		final long timestamp = System.currentTimeMillis();
		final String cp64 = (String) pa.getValue(COOKIE_PAYLOAD_B64);
		final String cookiePayload = UniBase64.decodeToString(cp64);
		final List<String> cookies = Splitters.byPattern("; ").splitToList(cookiePayload);
		ArrayUtils.printLln(cookies);
		final SAPIISIDInfo info = getFromCookies(timestamp, cookies);
		JVM.println("info: " + info);
		JVM.println("hash: " + info.makeHashedHeader());
		JVM.println("Requesting a video...");
		final IYouTubeExtractor ex = YouTubeExtractor.get("ytiapi.native-rolling");
		ex.enableInstanceDebugging(true);
		final Executable<ExtractedVideo> f = ex.generateTask(FastMaps.fixedUniMap(new Object[][]
			{
					{ IYouTubeExtractorConstants.MAP_TYPE_VIDEO_ID, "DiwSm6QgZM8" },
					{ IYouTubeExtractorConstants.MAP_TYPE_ENABLE_DEBUG_API_RESPONSE_PRINT, true },
					{ IYouTubeExtractorConstants.MAP_TYPE_SOLVED_SAPISID_HASH, info.makeHashedHeader() },
					{ IYouTubeExtractorConstants.MAP_TYPE_SOLVED_ID_TOKEN, (String) pa.getValue(USER_ID) },
					{ IYouTubeExtractorConstants.MAP_TYPE_PASSTHROUGH_COOKIES, cookiePayload } }));
		ExecutorManager.get().asyncSubmit(f);
		final long s0 = System.currentTimeMillis();
		JVM.println("result: " + f.waitTillCompletion());
		final long e0 = System.currentTimeMillis();
		JVM.println("Execution Time: " + (e0 - s0) + " ms");
		if (f.result() == null) {
			throw new NullPointerException("Failed to get a result!");
		}
		if (f.result().supportsLateExtraction()) {
			f.result().awaitCompletion();
			JVM.println("lateExtraction: COMPLETE");
		}
		if (!f.result().getResult().successful()) {
			JVM.println("Failed to extract.");
			FastIO.get().flush();
			JVM.exit();
			return;
		}
		if (f.result().hasCommentData()) {
			JVM.println("commentData: " + f.result().getCommentData());
		}
		if (f.result().getMetadata().hasHeatmap()) {
			final Executable<YouTubeVideoHeatmap> exh = f.result().getMetadata().getHeatmap();
			JVM.println("heatmap: " + exh);
			if (exh.hasFailed()) {
				JVM.errorln(exh.getException());
			}
		}
		final long s = System.currentTimeMillis();
		if (f.result().getMetadata() != null)
			JVM.println("enhanced description: " + f.result().getMetadata().getEnhancedDescription());
		final long e = System.currentTimeMillis();
		JVM.println("lat: " + (e - s) + " ms");
		if (f.result() != null) {
			final List<YouTubeVideo> result = f.waitTillCompletion().videos();
			ArrayUtils.printLln(result);
			JVM.println(f.result().getMetadata());
		}
		FastIO.get().flush();
		JVM.exit();
	}
}
